/******************************************************************
 * Copyright (c) 2024 Bocloud Technologies Co., Ltd.
 * installer is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain n copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 ******************************************************************/

package main

import (
	"flag"
	"fmt"
	"net/http"
	"net/http/pprof"
	"os"
	"strings"
	"time"

	agentv1beta1 "gopkg.openfuyao.cn/bkeagent/api/v1beta1"
	commonutils "gopkg.openfuyao.cn/bkecommon/utils"
	// cluster-api
	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
	controlv1beta1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
	clusterexpv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
	// k8s
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/util/workqueue"
	// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
	// to ensure that exec-entrypoint and run can make use of them.
	_ "k8s.io/client-go/plugin/pkg/client/auth"
	"k8s.io/client-go/tools/record"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/healthz"
	"sigs.k8s.io/controller-runtime/pkg/log/zap"
	//+kubebuilder:scaffold:imports

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/controllers"
	bkemetrics "gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/metrics"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/clustertracker"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/config"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/log"
	scriptshelper "gopkg.openfuyao.cn/cluster-api-provider-bke/utils/scriptshelper"
	v "gopkg.openfuyao.cn/cluster-api-provider-bke/version"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/webhooks"
)

var (
	scheme   = runtime.NewScheme()
	setupLog = ctrl.Log.WithName("setup")
)

func init() {
	//设置时区为上海
	loc, err := time.LoadLocation("Asia/Shanghai")
	if err == nil {
		time.Local = loc
		setupLog.Info("Set timezone to Asia/Shanghai")
	}

	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
	utilruntime.Must(clusterv1.AddToScheme(scheme))
	utilruntime.Must(clusterexpv1.AddToScheme(scheme))
	utilruntime.Must(agentv1beta1.AddToScheme(scheme))
	utilruntime.Must(controlv1beta1.AddToScheme(scheme))
	utilruntime.Must(bootstrapv1.AddToScheme(scheme))

	utilruntime.Must(bkev1beta1.AddToScheme(scheme))
	//+kubebuilder:scaffold:scheme
}

func main() {

	config.ConfigurationFlag()

	opts := zap.Options{
		Development: os.Getenv("DEBUG") == "true",
		Encoder:     log.Encoder,
	}
	opts.BindFlags(flag.CommandLine)

	flag.Parse()
	ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

	setupLog.Info("--------------Starting the BKE Cluster API Provider---------------")
	setupLog.Info(fmt.Sprintf("🤯 Version: %s", v.Version))
	setupLog.Info(fmt.Sprintf("🤔 GitCommitId: %s ", v.GitCommitID))
	setupLog.Info(fmt.Sprintf("👉 Architecture: %s", v.Architecture))
	setupLog.Info(fmt.Sprintf("⏲ BuildTime: %s", v.BuildTime))
	setupLog.Info("------------------------------------------------------------------")

	if manifestInfo, err := commonutils.GetManifestsBuildInfo(); err == nil {
		for _, v := range manifestInfo {
			setupLog.Info(v)
		}
	} else {
		setupLog.Info("(ignore)Failed to get manifests build info: %v", err)
	}

	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
		Scheme:                 scheme,
		MetricsBindAddress:     config.MetricsAddr,
		Port:                   config.WebhookPort,
		Host:                   config.WebhookHost,
		HealthProbeBindAddress: config.ProbeAddr,
		LeaderElection:         config.EnableLeaderElection,
		LeaderElectionID:       "e2b5373a.bocloud.com",
		CertDir:                config.WebhookCertDir,
		EventBroadcaster: record.NewBroadcasterWithCorrelatorOptions(record.CorrelatorOptions{
			KeyFunc:              EventAggregatorByMessageFunc,
			BurstSize:            10000,
			LRUCacheSize:         4096 * 2,
			MaxIntervalInSeconds: 30,
		}),
	})
	if err != nil {
		setupLog.Error(err, "unable to start manager")
		os.Exit(1)
	}

	if err := scriptshelper.CreateScriptsConfigMaps(mgr.GetClient()); err != nil {
		setupLog.Error(err, "unable to create scripts configmaps")
	}

	tracker, err := clustertracker.NewClusterCacheTracker(
		mgr,
		clustertracker.ClusterCacheTrackerOptions{
			Indexes: clustertracker.DefaultIndexes,
		},
	)
	if err != nil {
		setupLog.Error(err, "unable to create cluster cache tracker")
		os.Exit(1)
	}

	ctx := ctrl.SetupSignalHandler()
	if err = (&controllers.BKEClusterReconciler{
		Client:     mgr.GetClient(),
		Scheme:     mgr.GetScheme(),
		Recorder:   mgr.GetEventRecorderFor("bke-cluster"),
		RestConfig: mgr.GetConfig(),
		Tracker:    tracker,
	}).SetupWithManager(ctx, mgr, concurrency(config.BkeClusterConcurrency)); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "BKECluster")
		os.Exit(1)
	}
	if err = (&controllers.BKEMachineReconciler{
		Client:   mgr.GetClient(),
		Scheme:   mgr.GetScheme(),
		Recorder: mgr.GetEventRecorderFor("bke-machine"),
	}).SetupWithManager(mgr, concurrency(config.BkeMachineConcurrency)); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "BKEMachine")
		os.Exit(1)
	}

	if err = (&webhooks.BKECluster{Client: mgr.GetClient()}).SetupWebhookWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create webhook", "webhook", "BKECluster")
		os.Exit(1)
	}
	//+kubebuilder:scaffold:builder

	if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
		setupLog.Error(err, "unable to set up health check")
		os.Exit(1)
	}
	if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
		setupLog.Error(err, "unable to set up ready check")
		os.Exit(1)
	}

	registerMetric(mgr)

	registerProfiler(mgr)

	setupLog.Info("starting manager")
	if err := mgr.Start(ctx); err != nil {
		setupLog.Error(err, "problem running manager")
		os.Exit(1)
	}
}

// EventAggregatorByMessageFunc aggregates events by exact match on event.Source, event.InvolvedObject, event.Type,
// event.Reason, event.ReportingController and event.ReportingInstance
func EventAggregatorByMessageFunc(event *corev1.Event) (string, string) {
	return strings.Join([]string{
		event.Source.Component,
		event.Source.Host,
		event.InvolvedObject.Kind,
		event.InvolvedObject.Namespace,
		event.InvolvedObject.Name,
		string(event.InvolvedObject.UID),
		event.InvolvedObject.APIVersion,
		event.Type,
		event.Message,
		event.ReportingController,
		event.ReportingInstance,
	},
		""), event.Message
}

func concurrency(c int) controller.Options {
	recoverPanic := true
	return controller.Options{
		MaxConcurrentReconciles: c,
		RecoverPanic:            &recoverPanic,
		RateLimiter:             workqueue.NewItemFastSlowRateLimiter(2*time.Second, 60*time.Second, 10),
	}

}

func registerMetric(mgr ctrl.Manager) {
	if config.MetricsAddr == "0" {
		return
	}

	if err := mgr.AddMetricsExtraHandler("/export", bkemetrics.MetricRegister.HttpExportFunc()); err != nil {
		setupLog.Error(err, "unable to set up extra metrics handler")
		os.Exit(1)
	}
	if err := mgr.AddMetricsExtraHandler("/cluster", bkemetrics.MetricRegister.HttpClusterFunc()); err != nil {
		setupLog.Error(err, "unable to set up extra metrics handler")
		os.Exit(1)
	}
}

func registerProfiler(m ctrl.Manager) {
	if os.Getenv("DEBUG") != "true" && config.MetricsAddr == "0" {
		return
	}

	endpoints := map[string]http.HandlerFunc{
		"/debug/pprof/":        pprof.Index,
		"/debug/pprof/cmdline": pprof.Cmdline,
		"/debug/pprof/profile": pprof.Profile,
		"/debug/pprof/symbol":  pprof.Symbol,
		"/debug/pprof/trace":   pprof.Trace,
	}

	for path, handler := range endpoints {
		err := m.AddMetricsExtraHandler(path, handler)
		if err != nil {
			setupLog.Error(err, "unable to set up pprof handler")
			os.Exit(1)
		}
	}
}
